// Fill out your copyright notice in the Description page of Project Settings.


#include "Socket_Tibony.h"

#include <SocketSubsystem.h>
#include <IPAddress.h>
#include "Components/TextBlock.h"
#include "Components/EditableTextBox.h"
#include "Common/TcpListener.h"
#include "Runtime/Networking/Public/Interfaces/IPv4/IPv4Address.h"

//#include "Runtime/Networking/Public/Interfaces/IPv4/IPv4Endpoint.h"
//#include "Runtime/Sockets/Private/HoloLens/SocketSubsystemHoloLens.h"
#include "Runtime/Sockets/Public/IPAddress.h"
#include <Networking/Public/Interfaces/IPv4/IPv4Endpoint.h>
#include <Networking/Public/Common/TcpSocketBuilder.h>
#include <Networking/Public/Common/UdpSocketBuilder.h>

//ScreenMsg
void ScreenMsg(const FString& Msg)
{
	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, *Msg);
}
void ScreenMsg(const FString& Msg, const float Value)
{
	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("%s %f"), *Msg, Value));
}
void ScreenMsg(const FString& Msg, const FString& Msg2)
{
	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("%s %s"), *Msg, *Msg2));
}

// Sets default values

// Called when the game starts or when spawned


USocket_Tibony::USocket_Tibony()
{

}

USocket_Tibony::~USocket_Tibony()
{

}

void USocket_Tibony::CloseUDPSender(FName Platform)
{

	if (UDPSendSocket) //Clear all sockets!
	{
		UDPSendSocket->Close();
		ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(UDPSendSocket);
		UDPSendSocket = nullptr;
	}
}

FString USocket_Tibony::GetLocalIP()
{
	bool canBind = false;
	TSharedRef<FInternetAddr> localIp = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->GetLocalHostAddr(*GLog, canBind);

	return localIp->ToString(false);

}


bool USocket_Tibony::SetLength(int32 NewSize /*= 0*/)
{
	bool isGrow = false;

	if (innerBuffer.Num() < NewSize)
	{
		auto packetCount = NewSize / packetSize;
		if (NewSize % packetSize)
		{
			++packetCount;
		}
		innerBuffer.SetNum(packetSize * packetCount);

		isGrow = true;
	}

	currentSize = NewSize;

	return isGrow;
}

bool USocket_Tibony::ReceivedData()
{
	if (!ListenSocket->Wait(ESocketWaitConditions::WaitForRead, FTimespan::FromMilliseconds(10)))
	{
		return true;
	}

	if (!EnableSocket)
	{
		return true;
	}

	TSharedRef<FInternetAddr> targetAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
	//FInternetAddr* targetAddr;

	uint32 Size;

	while (EnableSocket && ListenSocket->HasPendingData(Size))
	{

		if (ListenSocket->HasPendingData(Size))
		{
			SetLength(Size);
			int32 BytesRead = 0;


			//ReceivedDataUnit.SetNumUninitialized(FMath::Min(Size, 65507u));
			ListenSocket->RecvFrom(innerBuffer.GetData(), innerBuffer.Num(), BytesRead, *targetAddr);


			SetLength(BytesRead);
			if (innerBuffer.Num() > 0)
			{
				FScopeLock lock(&ct);
				FString RemoteIP = targetAddr->ToString(false);
				TArray<uint8> Data = innerBuffer;
				AsyncTask(ENamedThreads::GameThread, [this, RemoteIP, Data]() {
					if (!ListenSocket) return;
					ReceiveDataEvent.Broadcast(RemoteIP, Data);
					});
				//ReceiveDataEvent.Broadcast(targetAddr->ToString(false), innerBuffer);
			}
			//TArray<uint8> out = innerBuffer;





			//RemoteIP = targetAddr->ToString(false);
			//return out;

		}



	}
	return true;
}


void USocket_Tibony::DataRecv()
{
	if (!EnableSocket)
	{
		return;
	}

	if (!ListenSocket)
	{
		return;
	}
	TSharedRef<FInternetAddr> targetAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
	//FInternetAddr* targetAddr;

	TArray<uint8> ReceivedData;
	uint32 Size;
	while (ListenSocket->HasPendingData(Size))
	{


		//uint8* Recv = new uint8[Size];
		int32 BytesRead = 0;

		ReceivedData.SetNumUninitialized(FMath::Min(Size, 65507u));
		ListenSocket->RecvFrom(ReceivedData.GetData(), ReceivedData.Num(), BytesRead, *targetAddr);

		ReceiveDataEvent.Broadcast(targetAddr->ToString(false), ReceivedData);


	}
	return;
}

bool USocket_Tibony::FormatIP4ToNumber(const FString& IpAddress, uint8(&Out)[4])
{
	auto _ip = IpAddress.ToLower();
	if (_ip == TEXT("localhost"))
	{
		Out[0] = 127;
		Out[1] = 0;
		Out[2] = 0;
		Out[3] = 1;
		return true;
	}

	_ip.Replace(TEXT(" "), TEXT(""));

	const TCHAR* Delims[] = { TEXT(".") };
	TArray<FString> Parts;
	_ip.ParseIntoArray(Parts, Delims, true);
	if (Parts.Num() != 4)
		return false;

	for (int32 i = 0; i < 4; ++i)
	{
		Out[i] = FCString::Atoi(*Parts[i]);
	}

	return true;
}

void USocket_Tibony::UDPSendTo(FString IP, int32 Port, const TArray<uint8>& DataBuffer)
{
	if (!EnableSocket)
	{
		return;
	}
	//auto endPoint = GetIP4EndPoint(IP, Port);
	uint8 IP4Nums[4];
	FormatIP4ToNumber(IP, IP4Nums);
	FIPv4Endpoint Endpoint(FIPv4Address(IP4Nums[0], IP4Nums[1], IP4Nums[2], IP4Nums[3]), Port);
	int32 BytesSent;
	if (UDPSendSocket) {
		UDPSendSocket->SendTo(DataBuffer.GetData(), DataBuffer.Num(), BytesSent, Endpoint.ToInternetAddr().Get());
	}


}

void USocket_Tibony::UDPCreateSender(int32 SendBufferSize)
{
	UDPSendSocket = FUdpSocketBuilder(TEXT("ObjectDeliverer UdpSocket Tibony"))
		.WithSendBufferSize(SendBufferSize)
		.AsNonBlocking()
		.Build();
}

void USocket_Tibony::CloseUDPReceiver()
{
	//delete UDPReceiver;
	//UDPReceiver = nullptr;

	//Clear all sockets!
	//      makes sure repeat plays in Editor dont hold on to old sockets!
	if (ListenSocket)
	{
		FScopeLock lock(&ct);
		if (!CurrentThread) return;
		CurrentThread->Kill(true);

		delete CurrentThread;
		CurrentThread = nullptr;

		if (!CurrentInnerThread) return;
		delete CurrentInnerThread;
		CurrentInnerThread = nullptr;

		ListenSocket->Close();
		ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ListenSocket);

		ListenSocket = nullptr;

	}












}

void USocket_Tibony::StartUDPReceiver(const FString& YourChosenSocketName, const FString& TheIP, const int32 ThePort, bool& success, FName Platform)
{
	TSharedRef<FInternetAddr> targetAddr = ISocketSubsystem::Get(Platform)->CreateInternetAddr();
	FIPv4Address Addr;
	FIPv4Address::Parse(TheIP, Addr);

	//Create Socket
	FIPv4Endpoint Endpoint(FIPv4Address::Any, ThePort);

	ListenSocket = FUdpSocketBuilder(*YourChosenSocketName)
		.AsNonBlocking()
		.AsReusable()
		.BoundToEndpoint(Endpoint)
		.WithReceiveBufferSize(1024 * 1024)

		;
	//BUFFER SIZE
	int32 BufferSize = 1024 * 1024;
	ListenSocket->SetSendBufferSize(BufferSize, BufferSize);
	ListenSocket->SetReceiveBufferSize(BufferSize, BufferSize);


	if (EnableThread)
	{
		///*
		if (ListenSocket)
		{
			SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
			CurrentInnerThread = new FMyODWorkerThread([this]
				{
					FScopeLock lock(&ct);
					return ReceivedData();
				});
			CurrentThread = FRunnableThread::Create(CurrentInnerThread, TEXT("ObjectDeliverer UDPSocket PollingThread"));



		}
		//*/
	}


	if (!ListenSocket)
	{
		ScreenMsg("No socket");
		success = false;

	}
	if (ListenSocket)
	{
		ScreenMsg("The receiver is initialized");
		success = true;
	}

	//return true;

}

